package carson.web.mvc;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Carson Chen
 */
public class CarsonActionController extends AbstractController {

    /**
     * Log category to use when no mapped handler is found for a request.
     *
     * @see #pageNotFoundLogger
     */
    public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";


    /**
     * Additional logger to use when no mapped handler is found for a request.
     *
     * @see #PAGE_NOT_FOUND_LOG_CATEGORY
     */
    protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);

    /**
     * request中获取映射到的URL的属性
     */
    public static final String PATH_WITHIN_HANDLER_MAPPING_ATTR =
            "org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping";

    /**
     * Object we'll invoke methods on. Defaults to this.
     */
    private Object delegate;

    /**
     * Handler methods, keyed by name
     */
    private final Map<String, Method> handlerMethodMap = new HashMap<String, Method>();

    private UrlPathHelper urlPathHelper = new UrlPathHelper();

    private static ThreadLocal<HttpServletRequest> request_threadLocal = new ThreadLocal<HttpServletRequest>();

    private static ThreadLocal<HttpServletResponse> response_threadLocal = new ThreadLocal<HttpServletResponse>();

    public static void setRequest(HttpServletRequest request) {
        request_threadLocal.set(request);
    }

    public static HttpServletRequest getRequest() {
        return request_threadLocal.get();
    }

    public static void setResponse(HttpServletResponse response) {
        response_threadLocal.set(response);
    }

    public static HttpServletResponse getResponse() {
        return response_threadLocal.get();
    }

    public static HttpSession getSession() {
        HttpSession session = getRequest().getSession(false);
        return session;
    }

    /**
     * 实例化 {@code CarsonActionController}
     * 查找handler methods
     */
    public CarsonActionController() {
        this.delegate = this;
        registerHandlerMethods(this.delegate);
        // We'll accept no handler methods found here - a delegate might be set later on.
    }

    /**
     * 注册代理对象的所有 handlers method
     */
    private void registerHandlerMethods(Object delegate) {
        this.handlerMethodMap.clear();

        // Look at all methods in the subclass, trying to find
        // methods that are validators according to our criteria
        Method[] methods = delegate.getClass().getMethods();
        for (Method method : methods) {
            // We're looking for methods with given parameters.
            if (isHandlerMethod(method)) {
                registerHandlerMethod(method);
            }
        }
    }

    /**
     * 判断方法是否是一个合适的handler method
     */
    private boolean isHandlerMethod(Method method) {
        Class<?> returnType = method.getReturnType();
        if (ModelAndView.class.equals(returnType) || AbstractResult.class.isAssignableFrom(returnType)) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            //只支持没有参数的
            if (parameterTypes.length == 0) return true;
        }
        return false;
    }


    /**
     * 注册方法为handler method
     */
    private void registerHandlerMethod(Method method) {
        if (logger.isDebugEnabled()) {
            logger.debug("Found action method [" + method + "]");
        }
        this.handlerMethodMap.put(method.getName().toLowerCase(), method);
    }

    /**
     * 找到 handler method 并调用
     *
     * @see #invokeNamedMethod
     * @see #handleNoSuchActionHandlingMethod
     */
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        try {
            String methodName = this.getHandlerMethodName(request);
            return invokeNamedMethod(methodName, request, response);
        } catch (NoSuchActionHandlingMethodException ex) {
            return handleNoSuchActionHandlingMethod(ex, response);
        }
    }

    /**
     * 检索 URL 对应的方法
     * Converts {@code null} values to NoSuchActionHandlingMethodExceptions.
     */
    private final String getHandlerMethodName(HttpServletRequest request)
            throws NoSuchActionHandlingMethodException {

        String urlPath = this.urlPathHelper.getLookupPathForRequest(request);

        String methodName = request.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTR).toString();
        int removeStartIndex = methodName.indexOf('/');
        if (removeStartIndex > 0) {
            methodName = methodName.substring(0, removeStartIndex);
        }
        if (methodName == null) {
            throw new NoSuchActionHandlingMethodException(urlPath, request.getMethod(), request.getParameterMap());
        }
        if (logger.isDebugEnabled()) {
            logger.debug("检索到方法名 '" + methodName + "' 对应的URL: " + urlPath);
        }
        return methodName;
    }

    /**
     * 处理异常NoSuchActionHandlingMethodException
     * <p>默认日志记录警告并返回404页面
     *
     * @param ex       the NoSuchActionHandlingMethodException to be handled
     * @param response current HTTP response
     * @return a ModelAndView to render, or {@code null} if handled directly
     * @throws Exception an Exception that should be thrown as result of the servlet request
     */
    protected ModelAndView handleNoSuchActionHandlingMethod(
            NoSuchActionHandlingMethodException ex, HttpServletResponse response)
            throws Exception {

        pageNotFoundLogger.warn(ex.getMessage());
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return null;
    }

    /**
     * 调用指定名称的方法
     * <p>Uses a custom exception handler if possible; otherwise, throw an
     * unchecked exception; wrap a checked exception or Throwable.
     */
    protected final ModelAndView invokeNamedMethod(
            String methodName, HttpServletRequest request, HttpServletResponse response) throws Exception {

        Method method = this.handlerMethodMap.get(methodName.toLowerCase());
        if (method == null) {
            throw new NoSuchActionHandlingMethodException(methodName, getClass());
        }

        try {
            //赋值request、response
            setRequest(request);
            setResponse(response);
            Object returnValue = method.invoke(this.delegate);
            return processReturnValue(returnValue);
        } catch (InvocationTargetException ex) {
            // The handler method threw an exception.
            return handleException(request, response, ex.getTargetException());
        } catch (Exception ex) {
            // The binding process threw an exception.
            return handleException(request, response, ex);
        }
    }

    /**
     * 处理方法执行的返回值
     * 如果是{@link AbstractResult}的实例，则调用executeResult，返回null
     * 如果是{@link ModelAndView},则直接返回
     */
    @SuppressWarnings("unchecked")
    private ModelAndView processReturnValue(Object returnValue) {
        if (returnValue instanceof AbstractResult) {
            ((AbstractResult) returnValue).executeResult(getRequest(), getResponse());
            return null;
        } else if (returnValue instanceof ModelAndView) {
            return (ModelAndView) returnValue;
        } else {
            return null;
        }
    }

    /**
     * 处理异常
     *
     * @param request  current HTTP request
     * @param response current HTTP response
     * @param ex       the exception that got thrown
     * @return a ModelAndView to render the response
     */
    private ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, Throwable ex)
            throws Exception {
        // If we get here, there was no custom handler or we couldn't invoke it.
        ReflectionUtils.rethrowException(ex);
        throw new IllegalStateException("Should never get here");
    }

    /**
     * 创建一个将指定对象序列化为 JSON 的 JsonResult 对象。
     *
     * @param data 要序列化的对象
     * @return 将指定对象序列化为 JSON 格式的 JsonResult 对象
     */
    protected JsonResult Json(Object data) {
        return new JsonResult(data);
    }

    /**
     * 创建一个将指定对象序列化为 JSON 的 JsonResult 对象。
     *
     * @param data        要序列化的对象
     * @param contentType 内容类型（MIME 类型）
     * @return 将指定对象序列化为 JSON 格式的 JsonResult 对象
     */
    protected JsonResult Json(Object data, String contentType) {
        return new JsonResult(data, contentType);
    }

    /**
     * 创建一个将指定对象序列化为 JSON 的 JsonResult 对象。
     *
     * @param data            要序列化的对象
     * @param contentType     内容类型（MIME 类型）
     * @param contentEncoding 内容编码
     * @return 将指定对象序列化为 JSON 格式的 JsonResult 对象
     */
    protected JsonResult Json(Object data, String contentType, String contentEncoding) {
        return new JsonResult(data, contentType, contentEncoding);
    }

    /**
     * 使用字符串和内容类型创建一个内容结果对象
     *
     * @param content 要写入到响应的内容
     * @return 内容结果实例
     */
    protected ContentResult Content(String content) {
        return new ContentResult(content);
    }

    /**
     * 使用字符串和内容类型创建一个内容结果对象
     *
     * @param content     要写入到响应的内容
     * @param contentType 内容类型（MIME 类型）
     * @return 内容结果实例
     */
    protected ContentResult Content(String content, String contentType) {
        return new ContentResult(content, contentType);
    }

    /**
     * 使用字符串和内容类型创建一个内容结果对象
     *
     * @param content         要写入到响应的内容
     * @param contentType     内容类型（MIME 类型）
     * @param contentEncoding 内容编码
     * @return 内容结果实例
     */
    protected ContentResult Content(String content, String contentType, String contentEncoding) {
        return new ContentResult(content, contentType, contentEncoding);
    }

    /**
     * 使用文件路径和内容类型创建一个 FilePathResult 对象
     *
     * @param filePath    要发送到响应的文件的路径
     * @param contentType 内容类型（MIME 类型）
     * @return 文件流结果对象
     */
    protected FilePathResult File(String filePath, String contentType) {
        return new FilePathResult(filePath, contentType);
    }

    /**
     * 使用文件路径和内容类型创建一个 FilePathResult 对象
     *
     * @param filePath         要发送到响应的文件的路径
     * @param contentType      内容类型（MIME 类型）
     * @param fileDownloadName 浏览器中显示的文件下载对话框内要使用的文件名
     * @return 文件流结果对象
     */
    protected FilePathResult File(String filePath, String contentType, String fileDownloadName) {
        return new FilePathResult(filePath, contentType, fileDownloadName);
    }

    /**
     * 使用 Stream 对象和内容类型创建一个 FileStreamResult 对象
     *
     * @param fileStream       要发送到响应的流
     * @param contentType      内容类型（MIME 类型）
     * @param fileDownloadName 浏览器中显示的文件下载对话框内要使用的文件名
     * @return 文件流结果对象
     */
    protected FileStreamResult File(InputStream fileStream, String contentType, String fileDownloadName) {
        return new FileStreamResult(fileStream, contentType, fileDownloadName);
    }

    /**
     * 使用字符串和内容类型创建一个内容结果对象
     *
     * @param fileContents     要发送到响应的二进制内容
     * @param contentType      内容类型（MIME 类型）
     * @param fileDownloadName 浏览器中显示的文件下载对话框内要使用的文件名
     * @return 文件内容结果对象
     */
    protected FileContentResult File(byte[] fileContents, String contentType, String fileDownloadName) {
        return new FileContentResult(fileContents, contentType, fileDownloadName);
    }

    /**
     * 创建一个 JavaScriptResult 对象
     *
     * @param javaScript 要在客户端上运行的 JavaScript 代码
     * @return 将脚本写入到响应的 JavaScriptResult 对象
     */
    protected JavaScriptResult JavaScript(String javaScript) {
        return new JavaScriptResult(javaScript);
    }

    /**
     * 创建一个重定向到指定的 URL 的 RedirectResult 对象
     *
     * @param url 要重定向到的 URL
     * @return 重定向结果对象
     */
    protected RedirectResult Redirect(String url) {
        return new RedirectResult(url);
    }

    protected <T> T getModel(Class<T> modelClass) {
        return getModel(modelClass, null);
    }

    protected <T> T getModel(Class<T> modelClass, String modelName) {
        JSONObject jsonObject = new JSONObject();
        String modelNameAndDot = !StringUtils.isEmpty(modelName) ? modelName + "." : null;
        Map<String, String[]> requestMap = getRequest().getParameterMap();
        for (Map.Entry<String, String[]> entry : requestMap.entrySet()) {
            String paraName = entry.getKey();
            String attrName;
            if (modelNameAndDot != null) {
                if (paraName.startsWith(modelNameAndDot)) {
                    attrName = paraName.substring(modelNameAndDot.length());
                } else {
                    continue;
                }
            } else {
                attrName = paraName;
            }
            String[] paraValueArray = entry.getValue();
            String paraValue = (paraValueArray != null && paraValueArray.length > 0) ? paraValueArray[0] : null;
            jsonObject.put(attrName, paraValue);
        }
        return jsonObject.toJavaObject(modelClass);
    }

    protected String getPara(int index) {
        String pathWithinHandlerMapping = getRequest().getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTR).toString();
        String[] paraArr = split(pathWithinHandlerMapping, '/');

        if (paraArr.length > index + 1) {
            return paraArr[index + 1];
        }
        return "";
    }

    protected String getPara(String paraName) {
        return getRequest().getParameter(paraName);
    }

    /**
     * <p>Splits the provided text into an array, separator specified.
     * This is an alternative to using StringTokenizer.</p>
     *
     * <p>The separator is not included in the returned String array.
     * Adjacent separators are treated as one separator.
     * For more control over the split use the StrTokenizer class.</p>
     *
     * <p>A {@code null} input String returns {@code null}.</p>
     *
     * <pre>
     * StringUtils.split(null, *)         = null
     * StringUtils.split("", *)           = []
     * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
     * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
     * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
     * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
     * </pre>
     *
     * @param str  the String to parse, may be null
     * @param separatorChar  the character used as the delimiter
     * @return an array of parsed Strings, {@code null} if null String input
     */
    private static String[] split(final String str, final char separatorChar) {
        return splitWorker(str, separatorChar, false);
    }

    private static String[] splitWorker(final String str, final char separatorChar, final boolean preserveAllTokens) {
        // Performance tuned for 2.0 (JDK1.4)

        if (str == null) {
            return null;
        }
        final int len = str.length();
        if (len == 0) {
            return new String[0];
        }
        final List<String> list = new ArrayList<String>();
        int i = 0, start = 0;
        boolean match = false;
        boolean lastMatch = false;
        while (i < len) {
            if (str.charAt(i) == separatorChar) {
                if (match || preserveAllTokens) {
                    list.add(str.substring(start, i));
                    match = false;
                    lastMatch = true;
                }
                start = ++i;
                continue;
            }
            lastMatch = false;
            match = true;
            i++;
        }
        if (match || preserveAllTokens && lastMatch) {
            list.add(str.substring(start, i));
        }
        return list.toArray(new String[list.size()]);
    }
}
